/*
    hdvb - Framework for sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Uses an EIT database with decoded/decodable strings to deduce tables
 * for Huffman decoding of Freeview HD strings in a table with the same
 * programmes.
 */

#include <glib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <hdvb/cacheddb.h>
#include <hdvb/chansdb.h>
#include <hdvb/dbcommon.h>
#include <hdvb/eitdb.h>

typedef struct {
    sqlite3 *crib_chans_db;
    sqlite3 *fv_chans_db;
    sqlite3 *crib_eit_db;
    sqlite3 *fv_eit_db;
    sqlite3_stmt *lookup_service_name_stmt;
} RevFVHDContext;

static void
show_usage(gboolean err)
{
    fprintf(err ? stderr : stdout,
        "Usage: revfvhd CRIBCHANS FVCHANS CRIBEIT FVEIT TABLE\n"
        "Examines strings in FVEIT. If they use Freeview HD Huffman encoding\n"
        "the equivalent programme will be looked up in CRIBEIT, using\n"
        "CRIBCHANS and FVCHANS to find equivalent channels by name. Tables\n"
        "discovered this way will be writtenas TABLE.t1 and TABLE.t2.\n"
        "Existing tables files will be added to. All other files are sqlite\n"
        "files as used by hdvb and must pre-exist.\n");
}

static sqlite3 *
open_chans_db(const char *filename)
{
    HdvbCachedDb *cdb = hdvb_cached_db_open(filename, TRUE, FALSE,
            NULL, HDVB_CHANSDB_VERSION, NULL);
    
    return cdb->db;
}

static sqlite3 *
open_eit_db(const char *filename)
{
    HdvbCachedDb *cdb = hdvb_cached_db_open(filename, TRUE, FALSE,
            NULL, HDVB_EIT_DB_VERSION, NULL);
    
    if (hdvb_eit_db_setup_decoder(cdb->db))
        return cdb->db;
    return NULL;
}

static const char *
lookup_service_name(sqlite3_stmt *stmt, int service_id)
{
    sqlite3_reset(stmt);
    sqlite3_bind_int(stmt, 1, service_id);
    if (hdvb_db_step(stmt) != SQLITE_ROW)
        return NULL;
    return (const char *) sqlite3_column_text(stmt, 0);
}

static gboolean
known_encoding(const guchar *blob, int len)
{
    if (!len)
        return TRUE;
    if (blob[0] == 0 || blob[0] >= 0x20)
        return TRUE;
    if (blob[0] <= 0xb || blob[0] == 0x10 || blob[0] == 0x11)
        return TRUE;
    if (blob[0] >= 0x13 && blob[0] <= 0x15)
        return TRUE;
    return FALSE;
}

static void
debug_event(int event_id, const char *service_name, const char *nort,
        const guchar *blob, int len)
{
    char *msg = g_strdup_printf("Event %04x on '%s' %s starts with: %02x",
            event_id, service_name, nort, blob[0]);
    if (len > 1)
    {
        char *tmp = msg;
        msg = g_strdup_printf("%s %02x", msg, blob[1]);
        g_free(tmp);
    }
    if (len > 2)
    {
        char *tmp = msg;
        msg = g_strdup_printf("%s %02x", msg, blob[2]);
        g_free(tmp);
    }
    g_debug(msg);
}

static void
process_event(sqlite3_stmt *event_stmt, RevFVHDContext *ctx)
{
    const guchar *name_blob = sqlite3_column_blob(event_stmt, 4);
    int name_len = sqlite3_column_bytes(event_stmt, 4);
    const guchar *text_blob = sqlite3_column_blob(event_stmt, 5);
    int text_len = sqlite3_column_bytes(event_stmt, 5);
    
    gboolean known_name = known_encoding(name_blob, name_len);
    gboolean known_text = known_encoding(text_blob, text_len);
    
    if (known_name && known_text)
        return;
    
    int service_id = sqlite3_column_int(event_stmt, 0);
    int event_id = sqlite3_column_int(event_stmt, 1);
    const char *service_name = lookup_service_name(
            ctx->lookup_service_name_stmt, service_id);
    if (!known_name)
        debug_event(event_id, service_name, "name", name_blob, name_len);
    if (!known_text)
        debug_event(event_id, service_name, "text", text_blob, text_len);
}

static gboolean
revfvhd(const char *crib_chans_name, const char *fv_chans_name,
        const char *crib_eit_name, const char *fv_eit_name,
        const char *tables_name)
{
    RevFVHDContext ctx;
    
    ctx.crib_chans_db = open_chans_db(crib_chans_name);
    ctx.fv_chans_db = ctx.crib_chans_db ? open_chans_db(fv_chans_name) : NULL;
    ctx.crib_eit_db = ctx.fv_chans_db ? open_eit_db(crib_eit_name) : NULL;
    ctx.fv_eit_db = ctx.crib_eit_db ? open_eit_db(fv_eit_name) : NULL;
    if (!ctx.fv_eit_db)
    {
        show_usage(TRUE);
        return FALSE;
    }
    
    ctx.lookup_service_name_stmt = hdvb_db_prepare_statement(ctx.fv_chans_db,
            "SELECT service_name FROM services WHERE service_id = ?");
            
    sqlite3_stmt *events_stmt = hdvb_db_prepare_statement(
            ctx.fv_eit_db,
            "SELECT events.service_id, events.event_id, start_time, duration,"
            "       event_name, event_text"
            "   FROM events, short_descriptors"
            "   WHERE events.service_id = short_descriptors.service_id"
            "       AND events.event_id = short_descriptors.event_id");
            
    while (hdvb_db_step(events_stmt) == SQLITE_ROW)
    {
        process_event(events_stmt, &ctx);
    }
    return TRUE;
}

int
main(int argc, char **argv)
{
    gboolean usage = FALSE;
    gboolean err = FALSE;
    
    g_type_init();
    if (argc != 6)
    {
        usage = TRUE;
        err = TRUE;
    }
    else if (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h") ||
            !strcmp(argv[1], "--usage"))
    {
        usage = TRUE;
    }
    else 
    {
        int n;
        
        for (n = 1; n < 5; ++n)
        {
            if (!g_file_test(argv[n], G_FILE_TEST_EXISTS))
            {
                usage = TRUE;
                err = TRUE;
                break;
            }
        }
    }
    if (usage)
    {
        show_usage(err);
        return err = TRUE;
    }
    return revfvhd(argv[1], argv[2], argv[3], argv[4], argv[5]) != TRUE;
}
